package org.budo.dubbo.protocol.http.util;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.budo.dubbo.protocol.http.exception.BudoRuntimeException;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.spring.context.aware.BudoApplicationContextAware;
import org.budo.support.spring.util.SpringUtil;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;

import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.utils.Assert;

/**
 * @author lmw
 */
public class BudoBeanUtil {
    private static final Logger log = LoggerFactory.getLogger(BudoBeanUtil.class);

    private static Map<String, Object> beanInstanceCacheMap = new ConcurrentHashMap<String, Object>();

    public static Object getOrInitBeanCached(String typeName) {
        Object beanInstance = beanInstanceCacheMap.get(typeName);
        if (null != beanInstance) {
            return beanInstance;
        }

        beanInstance = findOrNewBean(typeName);
        beanInstanceCacheMap.put(typeName, beanInstance);
        return beanInstance;
    }

    public static Object getBeanCached(Class<?> type, String name) {
        String typeName = null == type ? null : type.getName();
        String cacheKey = typeName + "#" + name;

        Object beanInstance = beanInstanceCacheMap.get(cacheKey);
        if (null != beanInstance) {
            return beanInstance;
        }

        ApplicationContext applicationContext = getApplicationContext();
        if (null == applicationContext) {
            String message = "#49 applicationContext not found, type=" + type;
            throw new IllegalStateException(message);
        }

        beanInstance = null != name //
                ? SpringUtil.getBean(applicationContext, name) //
                : SpringUtil.getBean(applicationContext, type);

        if (null == beanInstance) {
            return null;
        }

        beanInstanceCacheMap.put(cacheKey, beanInstance);
        return beanInstance;
    }

    private static Object findOrNewBean(String typeName) {
        ApplicationContext applicationContext = getApplicationContext();
        Assert.notNull(applicationContext, "#63 applicationContext is null");

        Object beanInstance = null;
        Class<?> type = ReflectUtil.classForName(typeName);
        String[] beanNamesForType = applicationContext.getBeanNamesForType(type); // webApplicationContext-is-null

        if (null == beanNamesForType || beanNamesForType.length < 1) {
            beanInstance = ReflectUtil.newInstance(type); // 未在Ioc中配置直接new
        } else if (1 == beanNamesForType.length) {
            beanInstance = applicationContext.getBean(beanNamesForType[0]); // 已在Ioc中配置
        } else { // Ioc中配置了多个，报错
            String message = "#78 ApplicationContext 里有多个 " + type + " 的实例, beanNamesForType=" + StringUtil.join(beanNamesForType);
            throw new BudoRuntimeException(message);
        }

        return beanInstance;
    }

    public static ApplicationContext getApplicationContext() {
        BudoApplicationContextAware applicationContextAware = BudoApplicationContextAware.getInstance();
        if (null != applicationContextAware) {
            return applicationContextAware.getApplicationContext();
        }

        WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
        if (null != webApplicationContext) {
            return webApplicationContext;
        }

        log.error("no BudoApplicationContextAware, and getCurrentWebApplicationContext returns null, no applicationContext");
        return null;
    }
}
